package edu.office.applicationRepository;

import edu.office.entities.*;
import edu.office.tools.EOMessage;
import edu.office.tools.EOMessage1;
import edu.office.tools.StaticFields;
import io.quarkus.panache.common.Sort;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.inject.Named;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.Part;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.sl.usermodel.TableCell;
import org.apache.poi.sl.usermodel.TextParagraph;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.xslf.usermodel.*;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.nodes.Node;
import org.jsoup.nodes.TextNode;
import org.jsoup.select.Elements;

import java.awt.*;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.*;
import java.util.Map.Entry;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;


@Named("knowledgeRepositoryA")
@ApplicationScoped
public class KnowledgeRepository extends ApplicationCommonRepository<Knowledge> {

    //For special students, maybe one student or more than one student
    //颜色由浅到深的10种颜色
    private final String[] colorStrArr = new String[]{"#FFE1FF", "#FFD39B", "#FFBBFF", "#FFAEB9", "#FF8C69", "#FF8247", "#FF7256", "#FF6347", "#FF34B3", "#FF0000"};
    private final Integer[] fontSizeArr = new Integer[]{32, 38, 44, 50, 56, 62, 68, 74, 80, 80};
    private final HashMap<Subject, List<Knowledge>> subjectSortedKnowledge = new HashMap<>();
    int levelCount = 5;
    @Inject
    EdgeAmongKnowledgeRepository edgeamongknowledgeRepository;
    @Inject
    SubjectRepository subjectRepository;
    @Inject
    PredicateRepository predicateRepository;
    @Inject
    QuestionRepository questionRepository;
    @Inject
    ReexaminationRepository reexaminationRepository;
    @Inject
    EdgeAmongKnowledgeRepository applicationEdgeamongknowledgeController;
    @Inject
    ReexaminationRepository applicationReexaminationController;
    @Inject
    KnowledgeRepository applicationKnowledgeController;
    @Inject
    LearningresourceRepository applicationLearningresourceController;
    //-------------------------------
    //================For the parents knowledge ===============
    //temKnowledges is those being selected in the manyListbox
    //private HashSet<Knowledge> predcessorKnowledgeSet = new HashSet<>(), candidateKnowledge = new HashSet<>();
    @Inject
    LeadpointRepository applicationLeadpointController;
    @Inject
    StudentRepository applicationStudentController;
      @Inject
    SubjectRepository applicationSubjectController;
    @Inject
    TeacherAdminRepository applicationTeacherAdmin;
    @Inject
    ParentRepository applicationParent;
    //在这里把application中的knowledge拿来，放在这儿，selectedMay从这里直接检索，避免到数据库中检查
    //-------------------------------
    //================For the parents knowledge ===============
    //temKnowledges is those being selected in the manyListbox
    HashMap<Integer, HashMap<Integer, Knowledge>> subjectKnowledges = new HashMap<>();
    private HashMap<Integer, String> levelMap = null;
    private HashMap<Integer, List<Knowledge>> levelNumberKnowledge = new HashMap<>();//避免每次调用都初始化

    public KnowledgeRepository() {
        for (int i = 1; i <= levelCount; i++) {
            //知识点所属层次--知识点、小节、节、章、篇
            List<Integer> levelNumers = new LinkedList<>();
            levelNumers.add(i);
        }
        //8846是布鲁姆认知的6层次理论
        Map<Integer, Knowledge> knowledgesInMemeory = new HashMap<>();
        knowledgesInMemeory.put(8846, find("ID", 8846).firstResult());
    }

    static boolean containHtmlTable(Document document) {
        Elements tables = document.select("table");
        return !tables.isEmpty();
    }

    static List<Integer> countRowsAndCols(Document document) {
        List<Integer> countList = new ArrayList<>();
        Elements table = document.select("table");
        for (Element ta : table) {
            int rowCount;
            int colCount = 0;
            Elements rows = table.select("tr");
            rowCount = rows.size();
            countList.add(rowCount);
            for (Element row : rows) {
                Elements cols = row.select("td, th");
                if (cols.size() > colCount) {
                    colCount = cols.size();
                }
            }
            countList.add(colCount);
        }
        return countList;
    }

    //    设置单元格边框格式
    static void setBorderColorAndWidth(List<Integer> totalCount, XSLFTable table) {
        for (int i = 0; i < totalCount.get(0); i++) {
            for (int j = 0; j < totalCount.get(1); j++) {
                table.getCell(i, j).setBorderColor(TableCell.BorderEdge.left, Color.BLACK);
                table.getCell(i, j).setBorderColor(TableCell.BorderEdge.top, Color.BLACK);
                table.getCell(i, j).setBorderColor(TableCell.BorderEdge.right, Color.BLACK);
                table.getCell(i, j).setBorderColor(TableCell.BorderEdge.bottom, Color.BLACK);
                table.getCell(i, j).setBorderWidth(TableCell.BorderEdge.left, 1.0);
                table.getCell(i, j).setBorderWidth(TableCell.BorderEdge.top, 1.0);
                table.getCell(i, j).setBorderWidth(TableCell.BorderEdge.right, 1.0);
                table.getCell(i, j).setBorderWidth(TableCell.BorderEdge.bottom, 1.0);
            }
        }
    }

    public List<Knowledge> getKnowledgeList4Subject(int type, Knowledge knowledge, Subject subject) {
        List<Knowledge> temKnowledges;
        temKnowledges = getKnowledgeList4Subject(subject);
        if (type == 0) {//说明是要从中选择其父类知识点，所以要删除自身
            temKnowledges.remove(knowledge);
        }
        return temKnowledges;
    }

    public List<Knowledge> getKnowledgeList4Subject(Subject subject) {
        //From major to subject, then to knowledge, therefore, first get the subject===========
        List<Knowledge> knowledgesList = new LinkedList<>();
        if (null != subject) {
            knowledgesList = find("SUBJECT_ID", Sort.by("name"), subject.getID()).list();
        }
        return knowledgesList;
    }

    public List<Integer> getKnowledgeId4Subject(Subject subject) {
        List<Integer> result = new LinkedList<>();
        List<Knowledge> knowledges = getKnowledgeList4Subject(subject);
        for (Knowledge knowledge : knowledges) {
            result.add(knowledge.getID());
        }
        return result;
    }

    public boolean isKnowledgeExisted(String knowledgeName, Subject subject) {
        List<Knowledge> ids = find("where name='" + knowledgeName.trim() + "'  and subject_id=" + subject.getID()).list();
        return (null != ids && !ids.isEmpty());
    }

    public List<Knowledge> searchByString(String searchedString, int subject) {
        //setDataChanged(false);
        List<Knowledge> result = new LinkedList<>();
        if (null != searchedString && !searchedString.trim().isEmpty()) {
            String searchString = "select * from knowledge where "
                    + "(locate('" + searchedString.toLowerCase() + "',LOWER(name))>0 or "
                    + "locate('" + searchedString.toLowerCase() + "',LOWER(details))>0)";
            if (0 == subject) {
                result = find(searchString).list();
            } else {
                result = find(searchString + " and  subject_id=" + subject).list();
            }
        }
        return result;
    }

    public String getName4KnowledgeList(List<Knowledge> knowledges) {
        StringBuilder sb = new StringBuilder();
        if (null != knowledges) {
            knowledges.forEach(knw -> sb.append(knw.getNAME()).append("|"));
        }
        return sb.toString();
    }

    public List<Knowledge> getKnowledgesList4LeadingPoint(Leadpoint leadpoint) {
        if (null == leadpoint || leadpoint.getKNOWLEDGE_ID() == null || leadpoint.getKNOWLEDGE_ID().isEmpty()) {
            return new ArrayList<>();
        } else {
            ArrayList<Knowledge> knowledgeList = new ArrayList<>();
            String[] knowledges = leadpoint.getKNOWLEDGE_ID().split(",");
            //setDataChanged(false);
            for (String knowledge : knowledges) {
                knowledgeList.add(find("ID", Integer.valueOf(knowledge)).firstResult());
            }
            return knowledgeList;
        }

    }

    //获得前驱
    public HashSet<Knowledge> getPredcessorKnowledges(Knowledge knowledge) {
        HashSet<Knowledge> predcessorKnowledgeSet = new HashSet<>();
        if (null != knowledge.getID()) {
            List<Edgeamongknowledge> edgeamongknowledges
                    = edgeamongknowledgeRepository.getEdgeamongknowledges4KnowledgePreOrSucc(knowledge.getID(), StaticFields.PREDCESSOR);
            edgeamongknowledges.forEach((Edgeamongknowledge edge) -> {
                //拿到了一条记录，在该记录中，knowledge作后继，因此，可以找到它的前驱
                predcessorKnowledgeSet.add(find("ID", edge.getPREDECESSORNODE()).firstResult());
            });
        }
        return predcessorKnowledgeSet;
    }

    public Set<Knowledge> getCandidateKnowledges(Subject subject, Knowledge removed) {
        HashSet<Knowledge> candidateKnowledge = new LinkedHashSet<>(getKnowledgeList4Subject(subject));
        candidateKnowledge.remove(removed);
        return candidateKnowledge;
    }

    public List<Map.Entry<Knowledge, Integer>> sortbyCountEntrys(Map<Knowledge, Integer> map) {
        if (map.isEmpty()) {
            return new ArrayList<>();
        }
        List<Map.Entry<Knowledge, Integer>> list = new ArrayList<>(map.entrySet());
        //降序排序
        list.sort((o1, o2) -> o2.getValue().compareTo(o1.getValue()));
        return list;
    }

    private void removeStyle(Knowledge knowledge) {
        if (null != knowledge.getDETAILS()) {
            if (knowledge.getDETAILS().contains("<td")) {
                knowledge.setDETAILS(StaticFields.replaceCommonStyle(knowledge.getDETAILS()));
            }
        }
    }

    public List<Knowledge> getKnowledgeList4Leadpoint(Leadpoint leadpoint) {
        return find("select * from knowledge where id in (" + leadpoint.getKNOWLEDGE_ID() + ") order by id").list();
    }

    public String getKnowledgeRelationship4dagre(Knowledge paraKnowledge) {
        if (null != paraKnowledge && null != paraKnowledge.getSUBJECT_ID()) {
            StringBuilder resultString = new StringBuilder("digraph {"
                    + "node [rx=5 ry=5 labelStyle=\"font: 300 14px 'Helvetica Neue', Helvetica\"] "
                    + "edge [labelStyle=\"font: 300 14px 'Helvetica Neue', Helvetica\"]\n");
            Set<Edgeamongknowledge> tem = edgeamongknowledgeRepository.getEdgeamongknowledgeList4Subject(subjectRepository.find("ID", paraKnowledge.getSUBJECT_ID()).firstResult());
            for (Edgeamongknowledge edge : tem) {
                if (edge.getPREDECESSORNODE().equals(paraKnowledge.getID())) {// 对当前的知识点进行字体处理
                    String fontSize = "font-size:32px;";
                    resultString.append(getShapeString(Knowledge.find("ID", edge.getPREDECESSORNODE()).firstResult(), fontSize));
                } else {
                    resultString.append(getShapeString(Knowledge.find("ID", edge.getPREDECESSORNODE()).firstResult(), ""));
                }
                if (edge.getSUCCESSORNODE().equals(paraKnowledge.getID())) {// 对当前的知识点进行字体处理
                    String fontSize = "font-size:32px;";
                    resultString.append(getShapeString(Knowledge.find("ID", edge.getSUCCESSORNODE()).firstResult(), fontSize));
                } else {
                    resultString.append(getShapeString(Knowledge.find("ID", edge.getSUCCESSORNODE()).firstResult(), ""));
                }
                resultString.append(edgeamongknowledgeRepository.getString4Dagre(edge));
            }
            return resultString + "}";
        } else {
            return "digraph {}";
        }
    }

    public String getString4Dagre(Knowledge knowledge) {
        return knowledge.getNAME().replace("\"", "") + knowledge.getID();
    }

    private String getShapeString(Knowledge knowledge, String fontsize) {
        StringBuilder sb = new StringBuilder();
        sb.append("\"").append(getString4Dagre(knowledge))
                .append("\"").append(" ");
        if (knowledge.getLEVELNUMBER() == null) {
            knowledge.setLEVELNUMBER(1);
        }
        switch (knowledge.getLEVELNUMBER()) {
            case 1:
                sb.append("[labelType=\"html\"  label=\"<span style='color:#000080;").append(fontsize).append("'>")
                        .append(getString4Dagre(knowledge))
                        .append("</span>\" shape=\"rect\"];\n");
                break;
            case 2:
                sb.append("[labelType=\"html\"  label=\"<span style='color:#000080;").append(fontsize).append("'>")
                        .append(getString4Dagre(knowledge))
                        .append("</span>\"];\n");
                break;
            case 3:
                sb.append("[labelType=\"html\"  label=\"<span style='color:#800000;").append(fontsize).append("'>")
                        .append(getString4Dagre(knowledge))
                        .append("</span>\" shape=\"circle\"];\n");
                break;
            case 4:
                sb.append("[labelType=\"html\"  label=\"<span style='color:#a30000;").append(fontsize).append("'>")
                        .append(getString4Dagre(knowledge))
                        .append("</span>\" shape=\"ellipse\"];\n");
                break;
            default:
                sb.append("[labelType=\"html\"  label=\"<span style='color:#5d1818;'").append(fontsize).append("'>")
                        .append(getString4Dagre(knowledge))
                        .append("</span>\" shape=\"circle\"];\n");
        }

        return sb.toString();
    }

    public Set<Knowledge> getKnowledges4Question(Set<Question> questionSet) {
        Set<Knowledge> result = new HashSet<>();
        for (Question question : questionSet) {
            result.add(find("ID", question.getKNOWLEDGE_ID()).firstResult());
        }
        return result;
    }

    public Set<Knowledge> getSuccessorKnowledges(Knowledge knowledge) {
        Set<Knowledge> kc = new HashSet<>();
        List<Edgeamongknowledge> edgeSet = edgeamongknowledgeRepository.getEdgeamongknowledges4KnowledgePreOrSucc(knowledge.getID(), StaticFields.PREDCESSOR);
        for (Edgeamongknowledge edgeamongknowledge : edgeSet) {
            kc.add(find("ID", edgeamongknowledge.getSUCCESSORNODE()).firstResult());
        }
        return kc;
    }

    public String getKnowledgeString(Set<Knowledge> knowledges) {
        StringBuilder result = new StringBuilder();
        for (Knowledge knowledge : knowledges) {
            result.append(",").append(knowledge.getID());
        }
        return !result.isEmpty() ? result.substring(1) : "";
    }

    public List<Knowledge> getSimilarKnowledges(String text, int type) {
        List<Knowledge> result = new LinkedList<>();
        List<Knowledge> allKnowledges = listAll();
        allKnowledges.forEach((Knowledge knwo) -> {
            double simlarity = 0;
            switch (type) {
                case 1://知识点名称
                    if (knwo.getNAME().trim().equals(text.trim()))
                        result.add(knwo);
                    break;
                case 2://知识点详情
                    if (knwo.getDETAILS().equals(text.trim())) result.add(knwo);
                    break;
            }
        });
        return result;
    }

    public String[] getColorStrArr() {
        return colorStrArr;
    }

    public Integer[] getFontSizeArr() {
        return fontSizeArr;
    }

    public Knowledge createStart(Subject subject) {
        Knowledge knowledge = new Knowledge();
        knowledge.setSUBJECT_ID(subject.getID());
        knowledge.setNAME(subject.getNAME() + " start");
        knowledge.setKNOWLEDGETYPE(0);
        persist(knowledge);
        subject.setSTARTKNOWLEDGE(knowledge.getID());
        return knowledge;
    }

    public void repaireKnwoledge() {
        repaireEndKnowledge();//修复终点
        repaireIsolated();//修复没有前驱的结点
        repaireEdgeAmongKnowledge();
        //删除重复的知识点
        removeRedundantKnowledge();
    }

    private void removeRedundantKnowledge() {

        for (Subject subject : subjectRepository.listAll()) {
            try {
                List<Knowledge> kept = find(
                        "select min(id) from KNOWLEDGE where subject_id=" + subject.getID() +
                                " and id is not null group by name having count(*)>1").list();
                HashMap<Knowledge, Knowledge> removedReplaced = new HashMap<>();//保存要删除的第1个knowledge和删除后用来替换的knowledge，在完成相关表的处理后，再删除
                for (Knowledge keptKnowledge : kept) {//得到要删除的知识点
                    List<Knowledge> duplicate = find("select * from KNOWLEDGE where name='" + keptKnowledge.getNAME() + "'").list();
                    for (Knowledge dele : duplicate) {
                        if (!Objects.equals(dele.getID(), keptKnowledge.getID())) {
                            removedReplaced.put(dele, keptKnowledge);
                        }
                    }
                }
                //更新Question表中记录
                for (Entry<Knowledge, Knowledge> entryKnowledge : removedReplaced.entrySet()) {
                    questionRepository.update("update question set KNOWLEDGE_ID=" + entryKnowledge.getValue().getID() + " where KNOWLEDGE_ID=" + entryKnowledge.getKey().getID());
                    reexaminationRepository.update("Update REEXAMINATION set KNOWLEDGEID=" + entryKnowledge.getValue().getID() + " where KNOWLEDGEID=" + entryKnowledge.getKey().getID());
                    reexaminationRepository.update("Update EDGEAMONGKNOWLEDGE set PREDECESSORNODE=" + entryKnowledge.getValue().getID() + " where PREDECESSORNODE=" + entryKnowledge.getKey().getID());
                    reexaminationRepository.update("Update EDGEAMONGKNOWLEDGE set SUCCESSORNODE=" + entryKnowledge.getValue().getID() + " where SUCCESSORNODE=" + entryKnowledge.getKey().getID());
                    reexaminationRepository.update("Update LEARNINGRESOURCE set KNOWLEDGE_ID=" + entryKnowledge.getValue().getID() + " where KNOWLEDGE_ID=" + entryKnowledge.getKey().getID());
                    reexaminationRepository.update("Update PRAISE set KNOWLEDGE_ID=" + entryKnowledge.getValue().getID() + " where KNOWLEDGE_ID=" + entryKnowledge.getKey().getID());
                }
                //删除冗余知识点
                for (Entry<Knowledge, Knowledge> entryKnowledge : removedReplaced.entrySet()) {
                    entryKnowledge.getKey().delete();
                }
            } catch (Exception e) {
                System.out.println("knowledgeRepository--removeRedundantKnowledge");
            }
        }
        repaireEdgeAmongKnowledge();//可能由于更新知识点Id，又带来了冗余，因此再删除一次

    }

    private void repaireEdgeAmongKnowledge() {
        //分两步走：1.获取冗余记录的id值；2.删除冗余的记录
        boolean finished = false;
        while (!finished) {//仍有冗余
            List<Edgeamongknowledge> kept = edgeamongknowledgeRepository.find(//要保留的记录的id值
                    "SELECT  min(id)   FROM EDGEAMONGKNOWLEDGE GROUP BY successornode, predecessornode   HAVING COUNT(*) > 1").list();
            if (!kept.isEmpty()) {
                for (Edgeamongknowledge id : kept) {
                    edgeamongknowledgeRepository.delete("delete from EDGEAMONGKNOWLEDGE where id=" + id);
                }
            } else {//没有冗余了
                finished = true;
            }
        }
    }

    private List<Subject> repaireEndKnowledge() {
        List<Subject> allSubject = subjectRepository.listAll();
        for (Subject subject : allSubject) {
            if (!list("SUBJECT_ID",subject.getID()).isEmpty()) {
                List<Knowledge> candiKnowledge = find("select * from knowledge where subject_id=" + subject.getID() + " and (name like '%end'or name like '%End')").list();
                Knowledge knowledge;
                if (!candiKnowledge.isEmpty()) {
                    knowledge = candiKnowledge.get(0);
                } else {
                    knowledge = new Knowledge();
                    knowledge.setSUBJECT_ID(subject.getID());
                    knowledge.setNAME(subject.getNAME() + " End");
                    knowledge.setKNOWLEDGETYPE(0);
                    persist(knowledge);
                }
                candiKnowledge = find("select * from knowledge where subject_id=" + subject.getID() + " and not (name like '%end'or name like '%End')").list();//不是结尾的知识点
                for (Knowledge knowledge1 : candiKnowledge) {
                    if (edgeamongknowledgeRepository.find("PREDECESSORNODE", knowledge1.getID()).list().isEmpty()) {//没有后继
                        Edgeamongknowledge tem = new Edgeamongknowledge();
                        tem.setPREDECESSORNODE(knowledge1.getID());
                        tem.setSUCCESSORNODE(knowledge.getID());
                        tem.setPREDICATE(predicateRepository.getPredecessor().getID());
                        edgeamongknowledgeRepository.persist(tem);
                    }
                }
            }
        }

        return allSubject;
    }

    private void repaireIsolated() {//修改独立点
        //因为已经修复了起始点和最终点，所以需要独立点修复，只需要连到起始点即可
        List<Subject> allSubject = subjectRepository.listAll();
        for (Subject subject : allSubject) {
            if (!list("SUBJECT_ID",subject.getID()).isEmpty()) {
                //查找既不是起点也不是终点的点
                List<Knowledge> candiKnowledge = find("select * from knowledge where subject_id=" + subject.getID() + " and not (name like '%end'or name like '%End') and not (name like '%start'or name like '%Start')").list();
                for (Knowledge knowledge1 : candiKnowledge) {
                    if (edgeamongknowledgeRepository.find("SUCCESSORNODE", knowledge1.getID()).list().isEmpty()) {//没有前驱
                        Edgeamongknowledge tem = new Edgeamongknowledge();
                        tem.setPREDECESSORNODE(subject.getSTARTKNOWLEDGE());
                        tem.setSUCCESSORNODE(knowledge1.getID());
                        tem.setPREDICATE(predicateRepository.getPredecessor().getID());
                        edgeamongknowledgeRepository.persist(tem);
                    }
                }
            }
            if (!edgeamongknowledgeRepository.find("PREDECESSORNODE", subject.getSTARTKNOWLEDGE()).list().isEmpty()) {
                System.out.println("起始结点的顶点太多");
            }
        }
    }

    public void resetSortedKnowledge(Subject subject) {
        if (null != subjectSortedKnowledge.get(subject)) {
            subjectSortedKnowledge.get(subject).clear();
        }
    }

    public List<Knowledge> getSortedKnowledges(Subject subject) {
        if (null == subjectSortedKnowledge.get(subject)) {
            subjectSortedKnowledge.put(subject, deepFirstVisit(find("ID", subject.getSTARTKNOWLEDGE()).firstResult()));
        }
        return subjectSortedKnowledge.get(subject);
    }

    private List<Knowledge> deepFirstVisit(Knowledge startKnowledge) {
        List<Knowledge> result = new LinkedList<>();
        // 创建一个栈来存储待访问的顶点
        Stack<Knowledge> stack = new Stack<>();
        stack.push(startKnowledge);

        while (!stack.isEmpty()) {
            // 弹出栈顶元素
            Knowledge vertex = stack.pop();
            result.add(vertex);
            // 获取当前顶点的邻居顶点
            Set<Edgeamongknowledge> neighborVertex
                    = new HashSet<>(edgeamongknowledgeRepository.find("PREDECESSORNODE", vertex.getID()).list());
            // 如果邻居顶点不为空且尚未访问，则将其加入栈中
            if (!neighborVertex.isEmpty()) {
                if (neighborVertex.size() > EdgeAmongKnowledgeRepository.MAXEDGES) {
                    System.out.println("知识点树太复杂");
                } else {
                    //首先通过levelNumber确定入栈的顺序，级别越低，访问越优先，因此入栈也越后，级别越高，访问越靠后，因此入栈优先
                    levelNumberKnowledge.clear();
                    for (Edgeamongknowledge edge : neighborVertex) {
                        levelNumberKnowledge.computeIfAbsent(find("ID", edge.getSUCCESSORNODE()).firstResult().getLEVELNUMBER(), k -> new LinkedList<>());
                        levelNumberKnowledge.get(find("ID", edge.getSUCCESSORNODE()).firstResult().getLEVELNUMBER()).add(find("ID", edge.getSUCCESSORNODE()).firstResult());
                    }
                    for (int i = levelCount - 1; i > 0; i--) {
                        try {//不是每个结点的后继结点都有前述所有类型的后继，只有部分有，而且要依据所属的levelNumber确定压入栈的顺序
                            for (Knowledge knowledge : levelNumberKnowledge.get(i)) {
                                stack.push(knowledge);
                            }
                        } catch (Exception e) {
                            System.out.println("knowledgeRepositorydeepFirstVisit");
                        }
                    }
                }
            }
        }
        return result;
    }

    public String getPreSuccNameString(Integer knowledgeId) {
        Knowledge thisKnowledge = find("ID", knowledgeId).firstResult();
        StringBuilder sb = new StringBuilder();
        Set<Edgeamongknowledge> preEdgeamongknowledges =
                new HashSet<>(edgeamongknowledgeRepository.find("SUCCESSORNODE", knowledgeId).list());
        Iterator<Edgeamongknowledge> it = preEdgeamongknowledges.iterator();
        while (it.hasNext()) {
            Edgeamongknowledge edgeamongknowledge = it.next();
            sb.append(find("ID", edgeamongknowledge.getPREDECESSORNODE()).firstResult().getNAME()).append(",");
        }
        sb.append("\n").append(thisKnowledge.getNAME()).append("\n");
        Set<Edgeamongknowledge> succEdgeamongknowledges =
                new HashSet<>(edgeamongknowledgeRepository.find("PREDECESSORNODE", knowledgeId).list());
        it = succEdgeamongknowledges.iterator();
        while (it.hasNext()) {
            Edgeamongknowledge edgeamongknowledge = it.next();
            sb.append(find("ID", edgeamongknowledge.getSUCCESSORNODE()).firstResult().getNAME()).append(",");
        }
        String result = sb.toString();
        if (result.endsWith(",")) {
            result = result.substring(0, result.length() - 1);
        }
        return result;
    }

    List<Knowledge> getMyRecorderKnowledgeList(User user, Integer status) {
        StringBuilder sb = new StringBuilder();
        List<Reexamination> reexaminations = reexaminationRepository.getReexaminationKnowledgeListRecorderBy(user, status);
        reexaminations.forEach(re -> sb.append(re.getKNOWLEDGEID()).append(","));

        String ids = sb.toString();
        if (!ids.isEmpty()) {
            ids = ids.endsWith(",") ? ids.substring(0, ids.length() - 1) : ids;
            return applicationKnowledgeController.list("select * from knowledge where id in (" + ids + ")");
        } else {
            return new LinkedList<>();
        }
    }

    public List<Knowledge> getMyExaminedKnowledgeList(Integer status, Integer secondStatus, TeacherAdmin user) {
        List<Knowledge> result;
        StringBuilder sb = new StringBuilder();
        List<Reexamination> reexaminations = reexaminationRepository.getReexaminationNeedToBeExamined(user, status, 0, secondStatus);
        reexaminations.forEach(re -> sb.append(re.getKNOWLEDGEID()).append(","));
        String ids = sb.toString();
        if (!ids.isEmpty()) {
            ids = ids.endsWith(",") ? ids.substring(0, ids.length() - 1) : ids;
            result = applicationKnowledgeController.list("select * from knowledge where id in (" + ids + ")");
        } else {
            result = new LinkedList<>();
        }
        return result;
    }

    public List<Knowledge> search(String searchName, Integer subjectId) {
        List<Knowledge> searchedResult;
        searchedResult = applicationKnowledgeController.searchByString(searchName, Objects.requireNonNullElse(subjectId, 0));
        return searchedResult;
    }

    public String showedName(Knowledge knowledge) {
        StringBuilder sb = new StringBuilder();
        List tem;
        if (null != knowledge) {
            Set<Reexamination> reexaminations = applicationReexaminationController.getReexamination4Knowledge(knowledge);
            if (!reexaminations.isEmpty()) {
                Reexamination reexamination = (Reexamination) reexaminations.toArray()[0];
                tem = applicationTeacherAdmin.list("id", reexamination.getTEACHERID());
                if (tem.isEmpty()) {//不是教师
                    tem = applicationStudentController.list("id", reexamination.getSTUDENTID());
                    if (tem.isEmpty()) {//不是学生
                        tem = applicationParent.list("id", reexamination.getPARENTID());
                    }
                }
                if (tem != null && !tem.isEmpty()) {//也可能为空
                    User temUser = (User) tem.get(0);
                    sb.append(null == temUser.getSecondname() ? "" : temUser.getSecondname()).append(" ")
                            .append(null == temUser.getFirstname() ? "" : temUser.getFirstname());
                }
            }
        }
        return sb.toString();
    }

    public HashMap<Integer, Knowledge> getSubjectKnowledges(Subject subject) {
        if (null == subjectKnowledges.get(subject.getID())) {
            List<Knowledge> knowledges = applicationKnowledgeController.getKnowledgeList4Subject(subject);
            HashMap<Integer, Knowledge> temMap = new HashMap<>();
            for (Knowledge knowledge : knowledges) {
                temMap.put(knowledge.getID(), knowledge);
            }
            subjectKnowledges.put(subject.getID(), temMap);
        }
        return subjectKnowledges.get(subject.getID());
    }

    public String MyCreate(Knowledge knowledge, Subject subject,User user) {
        //1. check whether the name existed? If true, deny the operation;else continue;
        if (!applicationKnowledgeController.isKnowledgeExisted(knowledge.getNAME(), subject)) {
            applicationKnowledgeController.create(knowledge);
            reexaminationRepository.createReexamination(knowledge, null,user);
            return EOMessage.getEOMessageENUM(EOMessage1.Succeed);
        } else {
            return EOMessage.getEOMessageENUM(EOMessage1.Already_Exist);
        }
    }

    public String MyUpdate(Knowledge knowledge,User user) {
        applicationKnowledgeController.edit();
        reexaminationRepository.createReexamination(knowledge, null,user);
        return EOMessage.getEOMessageENUM(EOMessage1.Succeed);
    }

    public void submit4Reexamination(Knowledge knowledge) {//提交审核
        Set<Reexamination> reexaminations = applicationReexaminationController.getReexamination4Knowledge(knowledge);
        if (!reexaminations.isEmpty()) {
            Reexamination tem = (Reexamination) reexaminations.toArray()[0];
            tem.setSTATUS(0);
            applicationReexaminationController.edit();
        }
    }

    public String knowledgesUpload(Part knowledgeExcelFile, Subject subject, User user) {
        List<NewOldID> newoldIds = new ArrayList<>();
        int i = 2;
        try {
            InputStream ins = knowledgeExcelFile.getInputStream();
            Workbook book = new HSSFWorkbook(ins);
            try (book) {
                Sheet sheet = book.getSheetAt(0);
                for (Row row : sheet) {
                    Knowledge knowledge = new Knowledge();
                    Edgeamongknowledge edgeamongknowledge = new Edgeamongknowledge();
                    //存储到数据库后产生的新的knowledge对象
                    Knowledge createdNewId;
                    //判断数据库是否存在sheet当前行的知识点名称，如果没有则写入
                    if (applicationKnowledgeController.list("select * from knowledge where name='" + row.getCell(2).getStringCellValue() + "' and subject_id=" + subject.getID()).isEmpty()) {
                        knowledge.setSUBJECT_ID(subject.getID());
                        knowledge.setNAME(row.getCell(2).getStringCellValue());
                        knowledge.setDETAILS(row.getCell(3).getStringCellValue());
                        applicationKnowledgeController.create(knowledge);
                        reexaminationRepository.createReexamination(knowledge, null,user);
                    }
                    List<Knowledge> knowledges = applicationKnowledgeController.list("select * from knowledge where name='" +
                            row.getCell(2).getStringCellValue().trim() + "' and subject_id=" + subject.getID());
                    //拿到当前行新的知识点id值
                    createdNewId = knowledges.get(0);
//                    newoldIdMap.put(createdNewId.getId(), Integer.parseInt(sheet.getCell(0, i).getContents()));
                    Integer preNode = null;
                    //第一个知识点前驱为null，判断当前前驱是否能成功转成数字
                    try {
                        preNode = Double.valueOf(row.getCell(1).getNumericCellValue()).intValue();
                    } catch (Exception e) {
                        //第一次写到map里面，如果之后前驱转换有错则不写入
                        newoldIds.add(new NewOldID(createdNewId.getID(), Double.valueOf(row.getCell(0).getNumericCellValue()).intValue()));
                        System.out.println("第一次写到map里面，如果之后前驱转换有错则不写入");
                    }
                    //如果成功转换，则遍历map找到此知识点的前驱和map中存储的旧的id值是否相同
                    if (preNode != null) {
                        for (NewOldID newOldID : newoldIds) {
                            if (Objects.equals(newOldID.oldId, preNode)) {
                                //如果相同，则把当前知识点的id和当前知识点前驱对应的新的id放到前驱后继关系表里面
                                edgeamongknowledge.setSUCCESSORNODE(createdNewId.getID());
                                edgeamongknowledge.setPREDECESSORNODE(newOldID.newId);
                                //判断数据库里面是否已经存在此关系，如果没有则写入
                                if (applicationEdgeamongknowledgeController.list("select * from EDGEAMONGKNOWLEDGE where successornode=" + edgeamongknowledge.getSUCCESSORNODE() + " and predecessornode=" + edgeamongknowledge.getPREDECESSORNODE()).isEmpty()) {
                                    applicationEdgeamongknowledgeController.create(edgeamongknowledge);
                                }
                                break;
                            }
                        }
                        //当遍历完map之后再存储新的id值和旧的id值到map里面
                        newoldIds.add(new NewOldID(createdNewId.getID(),
                                Double.valueOf(row.getCell(0).getNumericCellValue()).intValue()));
                    }

                }
            } catch (NumberFormatException e) {
                System.out.println("数值转换异常");
                i = -1;
            }
        } catch (IOException ex) {
            System.out.println("赋值-2");
            i = -2;
        }

        return switch (i) {
            case -2 -> EOMessage.getEOMessageENUM(EOMessage1.ExcelWrong3);
            case -1 -> EOMessage.getEOMessageENUM(EOMessage1.ExcelWrong2);
            case 2 -> EOMessage.getEOMessageENUM(EOMessage1.Import_Empty);
            default -> EOMessage.getEOMessageENUM(EOMessage1.Succeed);
        };
    }

    String getFilename(Part part) {
        for (String cd : part.getHeader("content-disposition").split(";")) {
            if (cd.trim().startsWith("filename")) {
                String filename = cd.substring(cd.indexOf('=') + 1).trim().replace("\"", "");
                return filename.substring(filename.lastIndexOf('/') + 1).substring(filename.lastIndexOf('\\') + 1); // MSIE fix.
            }
        }
        return null;
    }

    public String getAllStudentKnowledge4School(School school, Subject subject) {
        Set<Student> studentSet = new HashSet<>(applicationStudentController.find("SCHOOL_ID", school.getID()).list());
        String result = "digraph {"
                + "node [rx=5 ry=5 labelStyle=\"font: 300 14px 'Helvetica Neue', Helvetica\"] "
                + "edge [labelStyle=\"font: 300 14px 'Helvetica Neue', Helvetica\"]";
        return result + getKnowledgeRelationship4dagre(subject, studentSet) + "}";
    }

    Set<Knowledge> getKnowledgeToRecursion(Knowledge k) {
        Set<Knowledge> knowledgeSet = new HashSet<>();
        //递归结束条件
        //如果传入的知识点为空，返回null
        if (k == null) {
            return null;
        }
        //k不为空，放入当前知识点
        knowledgeSet.add(k);
        Set<Edgeamongknowledge> temEdAK = new HashSet<>(applicationEdgeamongknowledgeController.getEdgeamongknowledges4KnowledgePreOrSucc(k.getID(), StaticFields.SUCCESSOR));
        if (temEdAK.isEmpty()) {
            //后继为空，写入当前知识点集合
            return knowledgeSet;
        }
        for (Edgeamongknowledge eak : temEdAK) {
            k = applicationKnowledgeController.find("ID", eak.getSUCCESSORNODE()).firstResult();
            knowledgeSet.add(k);
            knowledgeSet.addAll(getKnowledgeToRecursion(k));
        }
        return knowledgeSet;
    }

    public void renameDuplicate(String name) {
        List<Knowledge> knoweList = applicationKnowledgeController.list("select * from knowledge where name='" + name.trim() + "'");
        knoweList.forEach(knowledge -> knowledge.setNAME(applicationSubjectController.find("ID", knowledge.getSUBJECT_ID()).firstResult().getNAME() + knowledge.getNAME()));
    }

    public void renameStartAndEnd() {
        this.renameDuplicate("start");
        this.renameDuplicate("end");
    }

    public List<Learningresource> getLearningResource(int type, Knowledge knowledge) {
        String sqlString = "select * from learningresource where knowledge_id=" + knowledge.getID() + " and type=" + type;
        return applicationLearningresourceController.list(sqlString);
    }

    public List<Learningresource> getImageResource(Knowledge knowledge) {
        return getLearningResource(StaticFields.IMAGETYPE, knowledge);
    }

    public List<Learningresource> getVedioResource(Knowledge knowledge) {
        return getLearningResource(StaticFields.VIDEOTYPE, knowledge);
    }

    public List<Learningresource> getAudioResource(Knowledge knowledge) {
        return getLearningResource(StaticFields.AUDIOTYPE, knowledge);
    }

    public List<Learningresource> getPDFResource(Knowledge knowledge) {
        return getLearningResource(StaticFields.PDFTYPE, knowledge);
    }

    public List<String> getResourceString(List<Learningresource> resourceList) {
        List<String> fileNames = new LinkedList<>();
        resourceList.forEach(resource -> {
            String[] names = resource.getVALUEINFO().split(",");
            fileNames.addAll(Arrays.asList(names));
        });
        return fileNames;
    }

    public List<Knowledge> getSimilarKnowledges(Knowledge knowledge) {
        int type = 0;
        if (null != knowledge.getNAME() && !knowledge.getNAME().trim().isEmpty()) {//如果正在查知识点名称
            type = 1;
        }
        //如果知识点细节也完善了，默认名称已经通过了，查细节部分
        if (null != knowledge.getDETAILS() && knowledge.getDETAILS().trim().length() > 13) {//因为可能只包含<p>&nbsp;</p>这个富文本
            type = 2;
        }
        if (type > 0) {
            return applicationKnowledgeController.getSimilarKnowledges(knowledge.getNAME(), type);
        }
        return new LinkedList<>();
    }

    public HashMap<Integer, String> getKnowledgeTypeMap() {
        HashMap<Integer, String> knowledgeTypeMap = new HashMap<>();
        knowledgeTypeMap.put(1, EOMessage.getEOMessageENUM(EOMessage1.K1nowledgeType));
        knowledgeTypeMap.put(2, EOMessage.getEOMessageENUM(EOMessage1.K2nowledgeType));
        knowledgeTypeMap.put(3, EOMessage.getEOMessageENUM(EOMessage1.K3nowledgeType));
        knowledgeTypeMap.put(4, EOMessage.getEOMessageENUM(EOMessage1.K4nowledgeType));
        return knowledgeTypeMap;
    }
    //参考https://poi.apache.org/components/slideshow/xslf-cookbook.html

    public String getKnowledgeRelationship4dagre4OneStudent(Subject subject, Student student) {
        String result = "digraph {"
                + "node [rx=5 ry=5 labelStyle=\"font: 300 14px 'Helvetica Neue', Helvetica\"] "
                + "edge [labelStyle=\"font: 300 14px 'Helvetica Neue', Helvetica\"]";
        if (subject == null || subject.getID() == null) {
            result += "}";
        } else {
            HashSet<Student> oneStudent = new HashSet<>();
            oneStudent.add(student);
            result += getKnowledgeRelationship4dagre(subject, oneStudent) + "}";
        }
        return result;
    }

    String getKnowledgeRelationship4dagre(Subject subject, Set<Student> studentSet) {
        List<Knowledge> leadingPoint = new LinkedList<>();
        if (!studentSet.isEmpty()) {
            for (Student student : studentSet) {
                //现在改为拿到学生最新的知识点前沿
                List<Leadpoint> leadpoints = applicationLeadpointController.getLeadpointList(student, subject);
                if (!leadpoints.isEmpty()) {
                    leadingPoint.addAll(applicationKnowledgeController.getKnowledgesList4LeadingPoint(leadpoints.get(0)));
                }

            }
        }
        StringBuilder resultString = new StringBuilder();
        //Color those knowledge on the leading point
        if (!leadingPoint.isEmpty() && studentSet.size() > 1) {
            Map<Knowledge, Integer> knowledgeCount = new HashMap<>();
            Set<Knowledge> leadingPointSet = new HashSet<>(leadingPoint);
            for (Knowledge k : leadingPointSet) {
                knowledgeCount.put(k, 1);
            }
            for (Knowledge current : leadingPoint) {
                for (Map.Entry<Knowledge, Integer> entry : knowledgeCount.entrySet()) {
                    if (Objects.equals(current.getID(), entry.getKey().getID())) {
                        entry.setValue(entry.getValue() + 1);
                        break;
                    }

                }
            }
            for (Map.Entry<Knowledge, Integer> entry : knowledgeCount.entrySet()) {
                int kCount = entry.getValue();
                //double fontSize = 32 + Math.ceil(kCount * 0.6);
                double colorRed = 1 + (double) (kCount * 9);
                if (colorRed > 255) {
                    colorRed = 255;
                }
                int colorIndex = (int) Math.ceil(colorRed / 25.5) - 1;
//                String colorStr = "rgb(" + colorRed + ",0,0)";
                resultString.append("\"").append(entry.getKey().getNAME()).append("\" [labelType=\"html\" label=\"<span style='font-size:").append(applicationKnowledgeController.getFontSizeArr()[colorIndex]).append("px;color:").append(applicationKnowledgeController.getColorStrArr()[colorIndex]).append(";'>").append(entry.getKey().getNAME()).append("</span>\"];");
            }
        } else {
            for (Knowledge current : leadingPoint) {
                resultString.append("\"").append(current.getNAME()).append("\" [labelType=\"html\" label=\"<span style='font-size:32px;color:red;'>").append(current.getNAME()).append("</span>\"];");
            }
        }
        Set<Edgeamongknowledge> tem = applicationEdgeamongknowledgeController.getEdgeamongknowledgeList4Subject(subject);
        for (Edgeamongknowledge edge : tem) {
            resultString.append(applicationEdgeamongknowledgeController.getString4Dagre(edge));
        }
        return resultString.toString();
    }

    public String getKeyKnowledge4SchoolSubject(School school, Subject subject) {
        List<Student> Students = applicationStudentController.list("SCHOOL_ID", school.getID());
        List<Knowledge> leadingPoint = new LinkedList<>();
        Map<Knowledge, Integer> knowledgeCount = new HashMap<>();
        if (!Students.isEmpty()) {
            for (Student temp : Students) {
                List<Leadpoint> leadpoints = applicationLeadpointController.getLeadpointList(temp, subject);
                if (!leadpoints.isEmpty()) {
                    leadingPoint.addAll(applicationKnowledgeController.getKnowledgesList4LeadingPoint(leadpoints.get(0)));
                }
            }

            if (!leadingPoint.isEmpty() && Students.size() > 1) {
                for (Knowledge k : leadingPoint) {
                    if (knowledgeCount.containsKey(k)) {
                        knowledgeCount.put(k, knowledgeCount.get(k) + 1);
                    } else {
                        knowledgeCount.put(k, 1);
                    }
                }
            } else {
                return null;
            }
        } else {
            return null;
        }
        StringBuilder sb = new StringBuilder();
        for (Map.Entry<Knowledge, Integer> entry : knowledgeCount.entrySet()) {
            sb.append(",")
                    .append(entry.getKey().getNAME())
                    .append(entry.getValue());
        }
        return sb.toString();
    }

    //    创建知识点PPT
    /*
     **使用的log4j是2.17，2.20出错
     */
    public String createPPT(List<Knowledge> pptKnowledges, HttpServletResponse response, Subject subject) throws IOException {
        XMLSlideShow ppt = new XMLSlideShow();
        //把knowledge按照依赖关系重新排序
        repaireRelationSship(pptKnowledges, subject);
        //
        for (Knowledge knowledge : pptKnowledges) {
            makePPT(ppt, knowledge);
        }
        //response.setContentType("application/octet-stream");
        // 设置响应的头部信息，包括文件名和内容长度
        //response.setContentLength(pptBytes.length);
        response.setContentType("application/vnd.openxmlformats-officedocument.presentationml.presentation");
//        response.setHeader("Content-Disposition", "attachment; filename=" + subjectController.getSelected().getName() + ".ppt");
        response.setHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode(subject.getNAME() + ".pptx", StandardCharsets.UTF_8));

        OutputStream out = null;
        try {
            out = response.getOutputStream();
            try (BufferedOutputStream bos = new BufferedOutputStream(out)) {
                ppt.write(bos);
            }
            out.flush();
        } catch (IOException ex) {
            Logger.getLogger(KnowledgeRepository.class.getName()).log(Level.SEVERE, null, ex);
            System.out.println("knowledgeController--createPPT");
        } finally {
            try {
                if (out != null) {
                    out.close();
                }
            } catch (IOException ex) {
                Logger.getLogger(KnowledgeRepository.class.getName()).log(Level.SEVERE, null, ex);
                System.out.println("knowledgeController--createPPT");
            }
        }
        return null;
    }

    void makePPT(XMLSlideShow ppt, Knowledge knowledge) {
        String subjectName = applicationSubjectController.getSubjectMap().get(knowledge.getSUBJECT_ID()).getNAME();
        String details = knowledge.getDETAILS();
        String knowledgeName = knowledge.getNAME();
        String levelNumber = knowledge.getLEVELNUMBER().toString();
        //添加空白幻灯片
        XSLFSlide slide = ppt.createSlide();
        //获取幻灯片的尺寸
        Dimension pageSize = ppt.getPageSize();
        //幻灯片添加文本框
        addTextBox(slide, subjectName, 0, 0, (int) pageSize.getWidth(), 40, "right", new Color(255, 255, 255));
        addTextBox(slide, knowledgeName, 0, 40, (int) pageSize.getWidth(), 40, "left", new Color(115, 148, 175));
//        addTextBox(slide, details, 0, 80, (int) pageSize.getWidth(), (int) pageSize.getHeight() - 120, "JUSTIFY", new Color(193, 193, 156));
        //具体内容部分处理
        dealKnowledgeDetails(ppt, slide, details);
        addTextBox(slide, levelNumber, 0, (int) pageSize.getHeight() - 40, (int) pageSize.getWidth(), 40, "left", new Color(115, 148, 175));
    }

    // 封装创建文本框的方法
    void addTextBox(XSLFSlide slide, String text, int x, int y, int width, int height, String location, Color color) {
        XSLFTextBox textBox = slide.createTextBox();
        XSLFTextParagraph paragraph = textBox.addNewTextParagraph();
        XSLFTextRun textRun = paragraph.addNewTextRun();
        textBox.setAnchor(new java.awt.Rectangle(x, y, width, height));
        textBox.setFillColor(color);
        textRun.setText(text);
        textRun.setFontFamily("Times New Roman");
        switch (location) {
            case "right":
                paragraph.setTextAlign(TextParagraph.TextAlign.RIGHT);
                break;
            case "left":
                paragraph.setTextAlign(TextParagraph.TextAlign.LEFT);
                break;
            default:
                paragraph.setTextAlign(TextParagraph.TextAlign.JUSTIFY);
        }
        // 设置文本样式
        textRun.setFontSize(14.0);
        textRun.setBold(true);
        textRun.setFontColor(Color.BLACK);
    }

    void dealKnowledgeDetails(XMLSlideShow ppt, XSLFSlide slide, String details) {
        if (null == details || details.isEmpty() || details.equalsIgnoreCase("<p>&nbsp;</p>")
                || details.equalsIgnoreCase("无") || details.equalsIgnoreCase("")) {
            XSLFTextBox textBox = slide.createTextBox();
            textBox.setAnchor(new Rectangle(0, 80, (int) ppt.getPageSize().getWidth(), (int) ppt.getPageSize().getHeight() - 120));
            XSLFTextParagraph paragraph = textBox.addNewTextParagraph();
            XSLFTextRun textRun = paragraph.addNewTextRun();
            textRun.setText("暂无数据");
            textRun.setFontSize(14.0);
        } else {
            if (containsHtmlTag(details)) {
                //字符串包含HTML标签
                convertHtmlToPPT(details, slide, ppt);
            } else {
                //字符串不包含html标签
                XSLFTextBox textBox = slide.createTextBox();
                textBox.setAnchor(new Rectangle(0, 80, (int) ppt.getPageSize().getWidth(), (int) ppt.getPageSize().getHeight() - 120));
                XSLFTextParagraph paragraph = textBox.addNewTextParagraph();
                XSLFTextRun textRun = paragraph.addNewTextRun();
                textRun.setText(details);
                textRun.setFontSize(14.0);
            }
        }
    }

    boolean containsHtmlTag(String input) {
        // 使用正则表达式检查字符串中是否包含 HTML 标签
        String htmlTagPattern = "<[^>]+>";
        Pattern pattern = Pattern.compile(htmlTagPattern);
        Matcher matcher = pattern.matcher(input);
        return matcher.find();
    }

    void convertHtmlToPPT(String details, XSLFSlide slide, XMLSlideShow ppt) {
        Document doc = Jsoup.parse(details);
        //判断是否是表格
        boolean hasTable = containHtmlTable(doc);
        if (hasTable == false) {
            //无表格
            converParagraphToPPT(doc, slide, ppt);
        } else {
            HTMLTableToPPT(doc, slide, ppt);
        }
    }

    //无表格的富文本处理
    void converParagraphToPPT(Document doc, XSLFSlide slide, XMLSlideShow ppt) {
        XSLFTextBox textBox = slide.createTextBox();
        StringBuilder sentenceBuilder = new StringBuilder();
        //判断是否含p标签
        //不包含p标签，只有其他块级标签
        if (doc.select("p").isEmpty()) {
            XSLFTextRun textRun = textBox.addNewTextParagraph().addNewTextRun();
            textRun.setFontSize(14.0);
            if (!doc.select("h1").isEmpty()) {
                sentenceBuilder.append(doc.select("h1").text()).append("\n");
                textRun.setFontSize(18.0);
            }
            if (!doc.select("h2").isEmpty()) {
                sentenceBuilder.append(doc.select("h2").text()).append("\n");
                textRun.setFontSize(16.0);
            }
            if (!doc.select("h3").isEmpty()) {
                sentenceBuilder.append(doc.select("h3").text()).append("\n");
                textRun.setFontSize(14.0);
            }
            if (!doc.select("ol").isEmpty()) {
                int index = 1;
                Elements listElement = doc.select("li");
                System.out.println(listElement);
                for (Element element : listElement) {
                    sentenceBuilder.append(index).append(":").append(element.text()).append("\n");
                    index++;
                }
            }
            if (!doc.select("ul").isEmpty()) {
                Elements listElement = doc.select("li");
                for (Element element : listElement) {
                    sentenceBuilder.append(element.text()).append("\n");
                }
            }
            textRun.setText(sentenceBuilder.toString());
        } else {
            //p标签
            parseHTMLAndInsert(doc, textBox);
        }
        textBox.setAnchor(new Rectangle(0, 80, (int) ppt.getPageSize().getWidth(), (int) ppt.getPageSize().getHeight() - 120));
    }

    void HTMLTableToPPT(Document doc, XSLFSlide slide, XMLSlideShow ppt) {
        boolean hasTabHeader = hasTableHeader(doc);
        List<Integer> rowAndCol = countRowsAndCols(doc);
        Elements rows = doc.select("tr");
        XSLFTable table = slide.createTable(rowAndCol.get(0), rowAndCol.get(1));
        table.setAnchor(new java.awt.Rectangle(0, 80, (int) ppt.getPageSize().getWidth(), (int) ppt.getPageSize().getHeight() - 120));
        setBorderColorAndWidth(rowAndCol, table);
        if (!hasTabHeader) {
            //无表头
            int rowIndex = 0;
            int colIndex = 0;
            int beforeMerge = 0;
            for (Element row : rows) {
                Elements tds = row.select("td");
                for (Element td : tds) {
                    if (td.attr("rowspan").isEmpty()) {
                        boolean flag = table.getCell(rowIndex, colIndex).getText().isEmpty();
                        if (flag) {
                            XSLFTextParagraph paragraph = table.getCell(rowIndex, colIndex).addNewTextParagraph();
                            XSLFTextRun textRun = paragraph.addNewTextRun();
                            textRun.setFontSize(12.0);
                            textRun.setText(td.text());
                        } else {
                            for (int col = colIndex; col <= rowAndCol.get(1); col++) {
                                if (table.getCell(rowIndex, col).getText().isEmpty()) {
                                    XSLFTextRun textRun = table.getCell(rowIndex, col).addNewTextParagraph().addNewTextRun();
                                    textRun.setFontSize(12.0);
                                    textRun.setText(td.text());
                                    break;
                                }
                            }
                        }
                    } else {
                        String rowspan = td.attr("rowspan");
                        int mergeNumber = Integer.parseInt(rowspan);
                        beforeMerge = beforeMerge + mergeNumber;
                        int i;
                        for (i = rowIndex; i < rowIndex + mergeNumber; i++) {
                            XSLFTextRun textRun = table.getCell(i, colIndex).addNewTextParagraph().addNewTextRun();
                            textRun.setFontSize(12.0);
                            textRun.setText(td.text());
                        }
                        table.mergeCells(rowIndex, rowIndex + mergeNumber - 1, colIndex, colIndex);
                    }
                    colIndex++;
                }
                rowIndex++;
                colIndex = 0;
            }
        } else {
            //有表头
            StringBuilder sentenceBuilder;
            for (int rowIndex = 0; rowIndex < rowAndCol.get(0); rowIndex++) {
                Element row = rows.get(rowIndex);
                XSLFTableRow tableRow = table.getRows().get(rowIndex);
                for (int colIndex = 0; colIndex < rowAndCol.get(1); colIndex++) {
                    Element cellElement = row.child(colIndex);
                    XSLFTableCell tableCell = tableRow.getCells().get(colIndex);
                    XSLFTextParagraph p = tableCell.addNewTextParagraph();
                    XSLFTextRun textRun = p.addNewTextRun();
                    String cellText = cellElement.html();
                    if (cellText.contains("<br>")) {
                        cellText = cellText.replaceAll("<br>", "\n");
                    }
                    if (cellText.contains("&nbsp;")) {
                        cellText = cellText.replaceAll("&nbsp;", "").trim();
                    }
                    if (!containsHtmlTag(cellText)) {
                        textRun.setFontSize(5.0);
                        textRun.setText(cellText);
                    } else {
                        sentenceBuilder = new StringBuilder();
                        if (!cellElement.select("p").isEmpty()) {
                            sentenceBuilder.append(cellElement.select("p").text()).append("\n");
                        }
                        if (!cellElement.select("ul").isEmpty()) {
                            Elements listElement = cellElement.select("li");
                            for (Element element : listElement) {
                                sentenceBuilder.append("·").append(element.text()).append("\n");
                            }
                        }
                        if (!cellElement.select("ol").isEmpty()) {
                            int index = 1;
                            Elements listElement = cellElement.select("li");
                            for (Element element : listElement) {
                                sentenceBuilder.append(index).append(":").append(element.text()).append("\n");
                                index++;
                            }
                        }
                        if (!cellElement.select("span").isEmpty()) {
                            sentenceBuilder.append(cellElement.select("span").text()).append("\n");
                        }
                        if (sentenceBuilder.isEmpty()) {
                            textRun.setText("000000");
                        } else {
                            textRun.setFontSize(5.0);
                            textRun.setText(sentenceBuilder.toString().trim());
                        }
                    }
                }
            }
        }
        //判断是否还含有table之外的其他标签
        int tableNum = doc.select("table").size();
        int divNum = doc.select("table,p,h1,h2,h3,ol,ul,li").not("table p,table ul,table ol,table li,table h1,table h2,table h3").size();
        if (divNum > tableNum) {
            Elements elements = doc.select("table,p,h1,h2,h3,ol,ul,li").not("table p,table ul,table ol,table li,table h1,table h2,table h3").not("table");
            XSLFTextBox textBox = slide.createTextBox();
            textBox.setAnchor(new Rectangle(50, 300, (int) ppt.getPageSize().getWidth(), (int) ppt.getPageSize().getHeight() - 300));
            XSLFTextParagraph paragraph = textBox.addNewTextParagraph();
            for (Element element : elements) {
                for (Node node : element.childNodes()) {
                    if (node instanceof TextNode) {
                        XSLFTextRun run = paragraph.addNewTextRun();
                        run.setText(((TextNode) node).text());
                        run.setFontSize(8.0);
                    } else if (node instanceof Element) {
                        process_html(node, paragraph);
                    }
                }
                paragraph.addNewTextRun().setText("\n");
            }
        }
    }

    // 检查表格是否有表头
    boolean hasTableHeader(Document document) {
        Elements tables = document.select("table");
        for (Element table : tables) {
            // 找到第一个<tr>元素
            Element firstRow = table.selectFirst("tr");
            if (firstRow != null) {
                // 检查第一行是否包含<th>元素
                Elements headerCells = firstRow.select("th");
                if (!headerCells.isEmpty()) {
                    return true;
                }
            }
        }
        return false;
    }

    void parseHTMLAndInsert(Document doc, XSLFTextBox textBox) {
        Element body = doc.body();
        Elements tags = body.select("p, h3, ul, ol");
        for (Element tag : tags) {
            XSLFTextParagraph paragraph = textBox.addNewTextParagraph();
            for (Node node : tag.childNodes()) {
                if (node instanceof TextNode) {
                    XSLFTextRun run = paragraph.addNewTextRun();
                    run.setText(((TextNode) node).text());
                } else if (node instanceof Element) {
                    process_html(node, paragraph);
                }
            }
        }
    }

    void process_html(Node node, XSLFTextParagraph paragraph) {
        // 定义正则表达式匹配HTML标签的模式
        String pattern = "<[^>]*>";
        // 创建Pattern对象
        Pattern tagPattern = Pattern.compile(pattern);
        // 创建Matcher对象
        Matcher tagMatcher = tagPattern.matcher(node.toString());
        // 使用正则表达式将HTML标签替换为空字符串
        String textWithoutTags = tagMatcher.replaceAll("");
        XSLFTextRun run = paragraph.addNewTextRun();
        run.setText(textWithoutTags);
        run.setFontSize(8.0);
        // 设置不同的文本段格式，例如粗体和斜体
        if (node.nodeName().equalsIgnoreCase("b") || node.nodeName().equalsIgnoreCase("strong")) {
            run.setBold(true);
        } else if (node.nodeName().equalsIgnoreCase("i")) {
            run.setItalic(true);
        } else if (node.nodeName().equalsIgnoreCase("br")) {
            run.setText("\n");
        } else if (node.nodeName().equalsIgnoreCase("a")) {
            run.setUnderlined(true);
        }
    }

    void repaireRelationSship(List<Knowledge> pptKnowledges, Subject subject) {
        //将knowledge按照遍历顺序排列，从而使得输出的PPT的逻辑合理
        if (!pptKnowledges.isEmpty()) {
            List<Knowledge> sortedKnowledges = applicationKnowledgeController.getSortedKnowledges(subject);
            Map<Knowledge, Integer> orderMap = new HashMap<>();
            for (int i = 0; i < sortedKnowledges.size(); i++) {
                orderMap.put(sortedKnowledges.get(i), i);
            }
            pptKnowledges.sort(Comparator.comparingInt(s -> Optional.ofNullable(orderMap.get(s)).orElse(Integer.MAX_VALUE)));
        }
    }

    //下面处理知识点关联关系
    //下面这个内部类用于记录当前用户的操作，如删除或新增知识点间关系，当持久化操作时把对应的这些记录写入到数据库中

    static class NewOldID {

        Integer newId;
        Integer oldId;

        public NewOldID(Integer newId, Integer oldId) {
            this.newId = newId;
            this.oldId = oldId;
        }
    }
}
